﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace DotNetCommon.Extensions;
/// <summary>
/// <see cref="FileInfo"/> 和 <see cref="DirectoryInfo"/>的扩展类
/// </summary>
public static class FileAndDirectoryInfoExtensions
{
    private const int DefaultBufferSize = 4096;
    private const char CarriageReturn = '\r';
    private const char NewLine = '\n';
    private const char Tab = '\t';

    /// <summary>
    /// 获取当前文件夹的大小(单位：B，包含子文件夹)
    /// </summary>
    public static long GetSizeInBytes(this DirectoryInfo directoryInfo)
    {
        var length = directoryInfo.GetFiles().AsParallel().Sum(file => file.Exists ? file.Length : 0);
        length += directoryInfo.GetDirectories().Sum(dir => dir.Exists ? dir.GetSizeInBytes() : 0);
        return length;
    }

    /// <summary>
    /// 获取当前文件夹的大小(人类易读的)
    /// </summary>
    public static string GetSizeInHumanize(this DirectoryInfo directoryInfo)
    {
        var length = GetSizeInBytes(directoryInfo);
        return UnitConverter.Humanize(length);
    }

    /// <summary>
    /// 判断当前文件夹是否是隐藏的
    /// </summary>
    public static bool IsHidden(this DirectoryInfo directoryInfo) => (directoryInfo.Attributes & FileAttributes.Hidden) != 0;

    /// <summary>
    /// 判断当前文件是否是隐藏的
    /// </summary>
    public static bool IsHidden(this FileInfo fileInfo) => (fileInfo.Attributes & FileAttributes.Hidden) != 0;

    /// <summary>
    /// 将当前文件重命名为 <paramref name="newName"/> 并返回新的文件<see cref="FileInfo"/>.
    /// </summary>
    public static FileInfo Rename(this FileInfo fileInfo, string newName)
    {
        AssertUtil.NotNull(fileInfo);
        AssertUtil.BeTrue<FileNotFoundException>(fileInfo.Exists, $"文件不存在({fileInfo.FullName})");

        AssertUtil.NotNullOrWhiteSpace(newName);
        AssertUtil.BeTrue<ArgumentException>(newName.IsValidFileName(), $"Invalid file name: '{newName}'");

        var renamedFilePath = Path.Combine(fileInfo.DirectoryName
                                           ?? throw new InvalidOperationException(), newName);
        fileInfo.MoveTo(renamedFilePath);
        return new FileInfo(renamedFilePath);
    }

    /// <summary>
    /// 以<see cref="FileShare.ReadWrite"/>方式，打开文件流，有<seealso cref="FileAccess.Read"/>权限
    /// </summary>
    public static FileStream OpenSequentialRead(this FileInfo file) =>
        new(
            file.FullName,
            FileMode.Open,
            FileAccess.Read,
            FileShare.ReadWrite,
            DefaultBufferSize,
            FileOptions.SequentialScan);

    /// <summary>
    /// 以<see cref="FileShare.ReadWrite"/>方式，新建或打开文件流，有<seealso cref="FileAccess.Read"/>权限
    /// </summary>
    public static FileStream OpenOrCreateSequentialRead(this FileInfo file) =>
        new(
            file.FullName,
            FileMode.OpenOrCreate,
            FileAccess.Read,
            FileShare.ReadWrite,
            DefaultBufferSize,
            FileOptions.SequentialScan);

    /// <summary>
    /// 以<see cref="FileShare.Read"/>方式，打开或文件流，有<seealso cref="FileAccess.Write"/>权限
    /// </summary>
    public static FileStream OpenOrCreateSequentialWrite(this FileInfo file) =>
        new(
            file.FullName,
            FileMode.OpenOrCreate,
            FileAccess.Write,
            FileShare.Read,
            DefaultBufferSize,
            FileOptions.SequentialScan);

    /// <summary>
    /// 以<see cref="FileShare.ReadWrite"/>方式，打开或文件流，有<seealso cref="FileAccess.ReadWrite"/>权限
    /// </summary>
    public static FileStream OpenOrCreateSequentialReadWrite(this FileInfo file) =>
        new(
            file.FullName,
            FileMode.OpenOrCreate,
            FileAccess.ReadWrite,
            FileShare.ReadWrite,
            DefaultBufferSize,
            FileOptions.SequentialScan);

    /// <summary>
    /// 以UTF8编码方式读取所有文本行
    /// </summary>
    public static IEnumerable<string> ReadLines(this FileInfo file) => file.ReadLines(Encoding.UTF8);

    /// <summary>
    /// 以指定的编码方式读取所有文本行
    /// </summary>
    public static IEnumerable<string> ReadLines(this FileInfo file, Encoding encoding)
    {
        AssertUtil.NotNull(file);
        AssertUtil.NotNull(encoding);

        using var fs = file.OpenOrCreateSequentialRead();
        using var reader = new StreamReader(fs, encoding);

        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }

    /// <summary>
    /// 根据指定的匹配规则<paramref name="searchPattern"/>列举出当前文件夹下的所有子文件夹
    /// </summary>
    public static IEnumerable<DirectoryInfo> EnumerateDirectoriesSafe(this DirectoryInfo directory,
        string searchPattern = "*",
        SearchOption option = SearchOption.TopDirectoryOnly,
        bool throwOnPathTooLong = false)
    {
        try
        {
            var directories = Enumerable.Empty<DirectoryInfo>();
            if (option == SearchOption.AllDirectories)
            {
                directories = directory.EnumerateDirectories()
                    .SelectMany(d => d.EnumerateDirectoriesSafe(searchPattern, option, throwOnPathTooLong));
            }

            return directories.Concat(directory.EnumerateDirectories(searchPattern));
        }
        catch (Exception ex) when (ex is UnauthorizedAccessException || ex is PathTooLongException && !throwOnPathTooLong)
        {
            return [];
        }
    }

    /// <summary>
    /// 根据指定的匹配规则<paramref name="searchPattern"/>列举出当前文件夹下的所有子文件
    /// </summary>
    public static IEnumerable<FileInfo> EnumerateFilesSafe(this DirectoryInfo directory,
        string searchPattern = "*",
        SearchOption option = SearchOption.TopDirectoryOnly,
        bool throwOnPathTooLong = false)
    {
        try
        {
            var files = Enumerable.Empty<FileInfo>();
            if (option == SearchOption.AllDirectories)
            {
                files = directory.EnumerateDirectories()
                    .SelectMany(d => d.EnumerateFilesSafe(searchPattern, option, throwOnPathTooLong));
            }

            return files.Concat(directory.EnumerateFiles(searchPattern));
        }
        catch (Exception ex) when (ex is UnauthorizedAccessException || ex is PathTooLongException && !throwOnPathTooLong)
        {
            return [];
        }
    }
}